# -*- coding: utf-8 -*-
# standard
import os
import sys
from typing import Tuple
from datetime import timezone
import time

# third
from pydantic import BaseModel
from pydantic import Field
from sqlalchemy import distinct
from sqlalchemy import case

# local
_P_PATH =  os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
if _P_PATH not in sys.path:
    sys.path.append(_P_PATH)
from db.sqlalchemy_dbc import MyBaseModel
from models.message_model import *
from models.event_model import *
from models.push_model import *
from models.celery_conf import CELERY_APP
from models.celery_conf import TIMEZONE


"""
@Title:   
@File: alert_func.py
@Author: walle 2024年12日30日 13时34分37秒
@Version: 1.0.0
@Desc: 
"""


@CELERY_APP.task
def add(x, y):
    return x + y


class TaskCancelReason(MyEnum):
    # 任务取消原因
    ARG_ERROR: str = "参数错误"
    ExPIRED: str = "任务过期"
    RECOVERY: str = "异常恢复"

    
class OutPushTask(BaseModel):
    """
    外推消息的任务
    """
    id: int | None = Field(None, description="消息ID OutPushTaskOrm.id")
    event_id: int = Field(..., description="事件ID MicServiceEventOrm.id")
    mic_service_source: MicServiceSource = Field(..., description="微服务来源")
    method: str = Field(..., description="通知方式 NotificationMethodName.value")
    content: str = Field(..., description="消息内容")
    targets: List[str] = Field(..., description="通知目标") # 邮箱、手机号等
    raw_obj_id: Union[int, str] = Field(..., description="原始对象ID") 
    raw_obj_status: int = Field(..., description="原始对象状态")
    customer_id: int = Field(..., description="品牌客户ID")
    customer_name: str = Field(..., description="品牌客户名")
    store_id: int = Field(..., description="门店ID")
    store_name: str = Field(..., description="门店名称")
    is_recovery_task: int = Field(0, description="是不是恢复的通知任务")
    alert_time: datetime = Field(..., description="告警发生/恢复的时间")
    plan_time: datetime = Field(..., description="计划发送时间")
    execute_time: datetime | None = Field(None, description="发送时间")
    
    def save_to_db(self, db_session: Session | None = None) -> None:
        """
        保存
        """
        db_session, need_close = (new_db_session(sync=True), True) if db_session is None else (db_session, False)
        kw = {
            "event_id": self.event_id,
            "customer_id": self.customer_id,
            "customer_name": self.customer_name,
            "store_id": self.store_id,
            "store_name": self.store_name,
            "raw_obj_id": self.raw_obj_id,
            "raw_obj_status": self.raw_obj_status,
            "is_recovery_task": self.is_recovery_task,
            "title": self.mic_service_source.value,
            "content": self.content,
            "method": self.method,
            "targets": self.targets,
            "alert_time": self.alert_time,
            "plan_time": self.plan_time,
        }
        orm = OutPushTaskOrm(**kw)
        db_session.add(orm)
        db_session.commit()
        db_session.refresh(orm)
        self.id = orm.id
        if need_close:
            db_session.close()
        return 
    
    async def save_to_db_async(self, db_session: AsyncSession | None = None) -> None:
        """
        保存
        """
        db_session, need_close = (new_db_session(), True) if db_session is None else (db_session, False)
        kw = {
            "event_id": self.event_id,
            "customer_id": self.customer_id,
            "customer_name": self.customer_name,
            "store_id": self.store_id,
            "store_name": self.store_name,
            "raw_obj_id": self.raw_obj_id,
            "raw_obj_status": self.raw_obj_status,
            "is_recovery_task": self.is_recovery_task,
            "title": self.mic_service_source.value,
            "content": self.content,
            "method": self.method,
            "targets": self.targets,
            "alert_time": self.alert_time,
            "plan_time": self.plan_time,
        }
        orm = OutPushTaskOrm(**kw)
        db_session.add(orm)
        await db_session.commit()
        await db_session.refresh(orm)
        self.id = orm.id
        if need_close:
            await db_session.close()
        return 

    def get_task_args(self) -> dict:
        """
        获取任务参数
        """
        return {
            "id": self.id,
            "event_id": self.event_id,
            "mic_service_source": self.mic_service_source.value,
            "method": self.method,
            "content": self.content,
            "targets": self.targets,
            "raw_obj_id": self.raw_obj_id,
            "raw_obj_status": self.raw_obj_status,
            "customer_id": self.customer_id,
            "customer_name": self.customer_name,
            "store_id": self.store_id,
            "store_name": self.store_name,
            "is_recovery_task": self.is_recovery_task,
            "alert_time": self.alert_time.strftime("%Y-%m-%d %H:%M:%S"),
            "plan_time": self.plan_time.strftime("%Y-%m-%d %H:%M:%S"),
        } 

    def alert_is_recovery(self, seconds: int = 60) -> Resp:
        """
        检查告警是不是已恢复？
        这需要在执行发送任务之前调用一次，因为告警状态可能会在发送任务之前发生改变
        :param seconds: 免检查的时间范围，单位是秒， 在这个 时间内免检查直接返回真值
        :return: resp.data = 0 表示正常，其他表示问题中
        """
        resp = Resp()
        if self.alert_time > (datetime.now() + timedelta(seconds=seconds)): #
            # 事件已经发生一段时间了，需要检查一下事件是否已恢复？
            auth = self.mic_service_source.get_authorization() 
            url = f"{self.mic_service_source.get_raw_obj_query_url()}"
            if self.mic_service_source.value == MicServiceSource.IOT.value:
                params = {"sensor_id": self.raw_obj_id}
            elif self.mic_service_source.value == MicServiceSource.GATEWAY.value:
                params = {"host_id": self.raw_obj_id}
            else:
                params = {"node_id": self.raw_obj_id}
            headers = {
                "Content-Type": "application/json",
                "Authorization": auth
            }
            resp = common_send_request(url=url, method="GET", params=params, headers=headers)
            if resp and int(resp.data) == RawObjectStatus.OK.value:  # 传感器状态已恢复
                self.cancel(TaskCancelReason.RECOVERY.value)
        else:
            resp.data = self.raw_obj_status
        return resp
    
    def add_to_task_queue(self) -> None:
        """
        添加到任务队列
        :return:
        """
        b1 = time.perf_counter()
        if self.execute_time is not None:
            logger.warning("This task has been execute")
        else:
            now = datetime.now()
            kw = self.get_task_args()
            b2 = time.perf_counter()
            logger.debug(f"获取任务参数耗时{(b2 - b1):6f}秒")
            task_id = f"{self.__class__.__name__}-{self.id}"
            offset = (self.plan_time - now).total_seconds()
            if offset <= 0:  # 如果超时，则立即发送
                logger.debug(f"{self.method}推送任务{task_id} 需要立即执行")
                self.in_queue()
                send_notification.apply_async(args=[kw], countdown=0, task_id=task_id)
                b3 = time.perf_counter()
                logger.debug(f"立即执行任务耗时{(b3 - b2):6f}秒")
            elif offset <= BEAT_INTERVAL * 60:  # 小于等于周期性任务的间隔，进行延时执行
                logger.debug(f"{self.method}推送任务{task_id} 计划于 {self.plan_time} 执行")
                self.in_queue()
                send_notification.apply_async(args=[kw], countdown=offset, task_id=task_id)
                b4 = time.perf_counter()
                logger.debug(f"延时执行任务耗时{(b4 - b2):6f}秒")
            else: # 大于周期性任务的执行间隔.忽略，让他们暂存在数据库中。
                logger.debug(f"{self.method}推送任务{task_id}的执行时间为{self.plan_time}, 超出本次最大延迟执行周期，先写入数据库")

    def in_queue(self):
        """
        标记一个任务已经入队列
        """
        db_session = new_db_session(sync=True)
        one = db_session.get(OutPushTaskOrm, self.id)
        if not one:
            logger.error(f"任务记录没有找到:{self.id}")
        else:
            one.status = PushTaskStatus.IN_QUEUE.value
            db_session.commit()
        db_session.close()

    def get_send_messages_kw(self) -> dict:
        """
        获取 MessageCenterTokenOrm.send_messages 所需的参数
        """
        kw = self.model_dump()
        kw['source'] = kw.pop("mic_service_source")
        return kw
    
    def execute(self):
        """
        执行
        :return:
        """
        kw = self.get_send_messages_kw()
        resp = MessageCenterTokenOrm.send_messages(**kw)
        db_session = new_db_session(sync=True)
        execute_time = datetime.now()
        self.execute_time = execute_time
        task = db_session.get(OutPushTaskOrm, self.id)
        if task is None:
            logger.error(f"错误，推送任务不存在，id = {self.id}")
        else:
            task.execute_time = execute_time 
            task.send_result = resp.data
            task.status = PushTaskStatus.COMPLETED.value if resp else PushTaskStatus.FAIL.value
            SendMessageRecordOrm.batch_insert_from_task(task=task, db_session=db_session)
            db_session.commit()
        db_session.close()
    
    def cancel(self, reason: TaskCancelReason):
        """
        标记任务已取消
        """ 
        db_session = new_db_session(sync=True)
        one = db_session.get(OutPushTaskOrm, self.id)
        if one is None:
            logger.error(f"错误，关联的推送任务不存在，id = {self.id}")
        else:
            one.status = PushTaskStatus.CANCEL.value
            one.reason = reason.value
            one.execute_time = datetime.now()
            db_session.commit()
        db_session.close()


class MicServiceEvent(MyBaseModel):
    """
    从微服务发来的原始消息
    recovery_time 为空，表示这是个新的事件，否则表示这个事件已经恢复
    如果事件源是一个恢复事件但找不到对应的事件，则表示这个事件已经恢复，而且不应该发送。
    todo: is_alert 为空和等于0表示这是一个预警，否则这是一个告警，将可能被 priority 字段取代
    """
    id: int | None = Field(None, description="事件ID Orm 对象的")
    customer_id: int = Field(..., description="品牌客户ID")
    customer_name: str = Field(..., description="品牌客户名称")
    store_id: int = Field(..., description="门店ID")
    store_name: str = Field(..., description="门店名称")
    message: str = Field(..., description="消息内容")
    is_alert: int = Field(1, description="是否是告警事件 这个字段可能会慢慢的被priority字段取代")
    priority: str = Field(EventPriority.WARNING.value, description="事件优先级")
    begin_value: str | None = Field(None, description="事件开始时的值")
    end_value: str | None = Field(None, description="事件结束时的值")
    alert_time: datetime = Field(..., description="事件发生时间")
    recovery_time: datetime | None = Field(None, description="事件恢复时间")
    push_time: datetime = Field(..., description="收到为服务端推送事件的时间")

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    @property
    def mes_type(self) -> str:
        if self.priority:
            return self.priority
        else: # todo: self.is_alert 将可能逐渐被 self.priority 取代
            return EventPriority.ALERT.value if self.is_alert else EventPriority.WARNING.value
        
    def allow_current_priority(self, strategy: NotificationStrategyOrm) -> bool:
        """
        判断当前优先级是否允许发送
        :param strategy: _description_
        :return: _description_
        """
        if strategy.priorities and self.mes_type not in strategy.priorities:
            return False
        else:
            return True

    def get_orm_class(self) -> type:
        """
        获取ORM类
        :return: _description_
        """
        if isinstance(self, IoTEvent):
            return IoTEventOrm
        elif isinstance(self, GatewayEvent):
            return GatewayEventOrm
        elif isinstance(self, LanEvent):
            return LanEventOrm
        else:
            raise ValueError("Unknown event type")
    
    def recovery_event_orm(self, db_session: Session) -> MicServiceEventOrm:
        """
        把指定事件标记为恢复
        :param db_session: _description_
        :return: _description_
        """
        raise NotImplementedError("请在子类中实现该方法")
    
    async def recovery_event_orm_async(self, db_session: AsyncSession) -> MicServiceEventOrm:
        """
        把指定事件标记为恢复
        :param db_session: _description_
        :return: _description_
        """
        raise NotImplementedError("请在子类中实现该方法")
    
    def send_recovery_messages(self):
        """
        发送事件恢复的通知消息
        """
        earliest_time = datetime.now() - timedelta(days=90)  # 设置一个很远的未来时间,超过这个时间的消息不再考虑
        b1 = time.perf_counter()
        db_session = new_db_session(sync=True)
        s = select(
            SendMessageRecordOrm.method,
            SendMessageRecordOrm.target,
        ).where(
            SendMessageRecordOrm.event_id == self.id,
            SendMessageRecordOrm.success == 1,
            SendMessageRecordOrm.send_time >= earliest_time
        )
        result: Dict[str, List[str]] = {} # key是通知方式，value是通知目标的数组
        total = 0
        for method, target in db_session.execute(s).all():
            total += 1
            container: List[str] = result.get(method, [])
            container.append(target)
            result[method] = container
        db_session.close()
        b2 = time.perf_counter()
        logger.debug(f"查询消息推送历史耗时:{(b2 - b1):6f}秒")
        if total == 0:
            logger.warning("没有找到对应的消息推送历史 无需发送对应的恢复记录")
        else:
            b3 = time.perf_counter()
            for method, targets in result.items():
                for target in targets:
                    b31 = time.perf_counter()
                    task = self.generate_recovery_message_task(method=method, target=target)
                    b32 = time.perf_counter()
                    logger.debug(f"生成恢复消息任务耗时:{(b32 - b31):6f}秒")
                    task.save_to_db(db_session=db_session)  # 保存待发送消息
                    b33 = time.perf_counter()
                    logger.debug(f"保存恢复消息任务耗时:{(b33 - b32):6f}秒")
                    task.add_to_task_queue() # 依次发送消息
                    b44 = time.perf_counter()
                    logger.debug(f"添加恢复消息任务到队列耗时:{(b44 - b33):6f}秒")
            b4 = time.perf_counter()
            logger.debug(f"发送恢复消息总耗时:{(b4 - b3):6f}秒")

    async def send_recovery_messages_async(self):
        """
        发送事件恢复的通知消息
        """
        earliest_time = datetime.now() - timedelta(days=90)  # 设置一个很远的未来时间,超过这个时间的消息不再考虑
        b1 = time.perf_counter()
        db_session = new_db_session()
        s = select(
            SendMessageRecordOrm.method,
            SendMessageRecordOrm.target,
        ).where(
            SendMessageRecordOrm.event_id == self.id,
            SendMessageRecordOrm.success == 1,
            SendMessageRecordOrm.send_time >= earliest_time
        )
        result: Dict[str, List[str]] = {} # key是通知方式，value是通知目标的数组
        total = 0
        query = await db_session.execute(s)
        for method, target in query.all():
            total += 1
            container: List[str] = result.get(method, [])
            container.append(target)
            result[method] = container
        b2 = time.perf_counter()
        logger.debug(f"查询消息推送历史耗时:{(b2 - b1):6f}秒")
        if total == 0:
            logger.warning("没有找到对应的消息推送历史 无需发送对应的恢复记录")
        else:
            b3 = time.perf_counter()
            for method, targets in result.items():
                for target in targets:
                    b31 = time.perf_counter()
                    # 生成恢复消息任务 generate_recovery_message_task 不是异步函数
                    task = self.generate_recovery_message_task(method=method, target=target)
                    b32 = time.perf_counter()
                    logger.debug(f"生成恢复消息任务耗时:{(b32 - b31):6f}秒")
                    await task.save_to_db_async(db_session=db_session)  # 保存待发送消息
                    b33 = time.perf_counter()
                    logger.debug(f"保存恢复消息任务耗时:{(b33 - b32):6f}秒")
                    task.add_to_task_queue() # 依次发送消息
                    b44 = time.perf_counter()
                    logger.debug(f"添加恢复消息任务到队列耗时:{(b44 - b33):6f}秒")
            b4 = time.perf_counter()
            logger.debug(f"发送恢复消息总耗时:{(b4 - b3):6f}秒")
        await db_session.close()
    
    def save_to_db(self, db_session: Session | None = None) -> Resp:
        """
        保存事件
        :return:
        """
        resp = Resp()
        db_session, need_close = (new_db_session(sync=True), True) if db_session is None else (db_session, False)
        try:
            cls = self.get_orm_class()
        except Exception as e:
            resp.error(e)
        if not resp:
            pass
        else:
            if self.recovery_time:  # 有恢复时间，是恢复事件
                orm = self.recovery_event_orm(db_session=db_session)
            else:
                kw = self.model_dump(exclude=['raw_obj_id'], exclude_none=True)
                orm = cls(**kw)
                db_session.add(orm)
            if orm:
                db_session.commit()
                db_session.refresh(orm)
                self.id = orm.id
            else:
                resp.message = "对应的事件记录的orm对象不存在, 无需执行恢复动作"
                logger.warning(resp.message)
        if need_close:
            db_session.close()
        return resp
    
    async def save_to_db_async(self, db_session: AsyncSession | None = None) -> Resp:
        """
        保存事件
        :return:
        """
        resp = Resp()
        db_session, need_close = (new_db_session(), True) if db_session is None else (db_session, False)
        try:
            cls = self.get_orm_class()
        except Exception as e:
            resp.error(e)
        if not resp:
            pass
        else:
            if self.recovery_time:  # 有恢复时间，是恢复事件
                orm = await self.recovery_event_orm_async(db_session=db_session)
            else:
                kw = self.model_dump(exclude=['raw_obj_id'], exclude_none=True)
                orm = cls(**kw)
                db_session.add(orm)
            if orm:
                await db_session.commit()
                await db_session.refresh(orm)
                self.id = orm.id
            else:
                resp.message = "对应的事件记录的orm对象不存在, 无需执行恢复动作"
                logger.warning(resp.message)
        if need_close:
            await db_session.close()
        return resp
    
    def generate_task(self) -> None:
        """
        生成任务
        :return: _description_
        """
        b1 = time.perf_counter()
        source = self.get_notification_source()  # 获取通知来源
        b2 = time.perf_counter()
        logger.debug(f"获取通知来源耗时 {(b2 - b1):6f}秒")
        db_session = new_db_session(sync=True)
        b3 = time.perf_counter()
        logger.debug(f"创建数据库连接耗时 {(b3 - b2):6f}秒")
        resp = self.save_to_db(db_session=db_session)  # 保存微服务事件，如果是恢复事件，这里会处理的
        b4 = time.perf_counter()
        logger.debug(f"保存微服务事件耗时 {(b4 - b3):6f}秒")
        if not resp:  # 保存失败
            db_session.close()
            return
        if self.recovery_time:  # 是恢复事件，发送事件恢复的通知消息
            self.send_recovery_messages()
            b5 = time.perf_counter()
            logger.debug(f"发送事件恢复的通知消息耗时 {(b5 - b4):6f}秒")
        else:  # 发送事件的通知消息
            b5 = time.perf_counter()
            tasks: List[OutPushTask] = []
            strategies = self.get_notification_strategies(source=source, db_session=db_session)  # 获取通知策略
            b6 = time.perf_counter()
            logger.debug(f"获取通知策略耗时 {(b6 - b5):6f}秒")
            if not strategies:
                logger.warning(f"没有找到对应的推送策略！ customer_id={self.customer_id}, store_id={self.store_id}, source={source}")
            else:
                # 检查通知策略
                b7 = time.perf_counter()
                for strategy in strategies:
                    methods: List[NotificationMethodOrm] = strategy.methods
                    for method in methods:
                        b71 = time.perf_counter()
                        task = self.new_push_messages_task(method=method, source=source)
                        b72 = time.perf_counter()
                        task.save_to_db(db_session=db_session)  # 保存待发送消息
                        b73 = time.perf_counter()
                        tasks.append(task)
                        logger.debug(f"创建消息任务耗时 {(b72 - b71):6f}秒")
                        logger.debug(f"保存消息任务耗时 {(b73 - b72):6f}秒")
                b8 = time.perf_counter()
                logger.debug(f"检查全部通知策略耗时 {(b8 - b7):6f}秒")
            db_session.close()
            # 依次发送消息
            for task in tasks:
                b9 = time.perf_counter()
                task.add_to_task_queue()
                b10 = time.perf_counter()
                logger.debug(f"添加消息任务到队列耗时 {(b10 - b9):6f}秒")

    async def generate_task_async(self) -> None:
        """
        生成任务
        :return: _description_
        """
        source = self.get_notification_source()  # 获取通知来源
        db_session = new_db_session()
        resp = await self.save_to_db_async(db_session=db_session)  # 保存微服务事件，如果是恢复事件，这里会处理的
        if not resp:  # 保存失败
            await db_session.close()
            return
        if self.recovery_time:  # 是恢复事件，发送事件恢复的通知消息
            await db_session.close()
            await self.send_recovery_messages_async()
        else:  # 发送事件的通知消息
            tasks: List[OutPushTask] = []
            strategies = await self.get_notification_strategies_async(source=source, db_session=db_session)  # 获取通知策略
            if not strategies:
                logger.warning(f"没有找到对应的推送策略！ customer_id={self.customer_id}, store_id={self.store_id}, source={source}")
            else:
                # 检查通知策略
                for strategy in strategies:
                    if not await strategy.in_send_time_async():
                        logger.warning(f"通知策略不在发送时间范围内！ customer_id={self.customer_id}, store_id={self.store_id}, source={source}, strategy_id={strategy.id}, message={self.message}")
                        continue
                    if not self.allow_current_priority(strategy):
                        logger.warning(f"通知策略的优先级不满足当前事件！ customer_id={self.customer_id}, store_id={self.store_id}, source={source}, strategy_id={strategy.id}, {self.mes_type} not in {strategy.priorities}, message={self.message}")
                        continue
                    methods: List[NotificationMethodOrm] = strategy.methods
                    for method in methods:
                        task = await self.new_push_messages_task_async(method=method, source=source)
                        await task.save_to_db_async(db_session=db_session)  # 保存待发送消息
                        tasks.append(task)
            await db_session.close()
            # 依次把发送消息的任务放入工作队列
            for task in tasks:
                task.add_to_task_queue()
        
    def new_push_messages_task(self, method: NotificationMethodOrm, source: NotificationSource) -> OutPushTask:
        """
        创建一个消息任务
        :return:
        """
        return OutPushTask(**self.generate_messages_task_args(source=source, method=method))
    
    async def new_push_messages_task_async(self, method: NotificationMethodOrm, source: NotificationSource) -> OutPushTask:
        """
        创建一个消息任务
        :return:
        """
        return OutPushTask(**await self.generate_messages_task_args_async(source=source, method=method))
    
    def generate_messages_task_args(self, source: NotificationSource, method: NotificationMethodOrm) -> dict:
        """
        生成消息任务的参数
        :return: _description_
        """
        targets = method.get_targets()
        method_name = method.name
        kw = {
            "event_id": self.id,
            "mic_service_source": MicServiceSource.get_instance_from_notification_source(source),
            "content": self.generate_content(method_name),
            "targets": targets,
            "raw_obj_id": self.get_raw_obj_id(),    
            "raw_obj_status": self.get_raw_obj_status(),    
            "alert_time": self.alert_time,
            "customer_id": self.customer_id,
            "customer_name": self.customer_name,
            "store_id": self.store_id,
            "store_name": self.store_name,
            "method": method_name,
            "plan_time": datetime.now() + timedelta(seconds=method.delay),
        }
        return kw
    
    async def generate_messages_task_args_async(self, source: NotificationSource, method: NotificationMethodOrm) -> dict:
        """
        生成消息任务的参数
        :return: _description_
        """
        targets = await method.get_targets_async()
        method_name = method.name
        kw = {
            "event_id": self.id,
            "mic_service_source": MicServiceSource.get_instance_from_notification_source(source),
            "content": self.generate_content(method_name),
            "targets": targets,
            "raw_obj_id": self.get_raw_obj_id(),    
            "raw_obj_status": self.get_raw_obj_status(),    
            "alert_time": self.alert_time,
            "customer_id": self.customer_id,
            "customer_name": self.customer_name,
            "store_id": self.store_id,
            "store_name": self.store_name,
            "method": method_name,
            "plan_time": datetime.now() + timedelta(seconds=method.delay),
        }
        return kw
    
    def generate_recovery_message_task(self, method: str, target: str) -> OutPushTask:
        """
        生成一个回复消息的任务
        :return: _description_
        """
        targets = [target]
        source = self.get_notification_source()
        kw = {
            "event_id": self.id,
            "mic_service_source": MicServiceSource.get_instance_from_notification_source(source),
            "content": self.generate_content(method=method),
            "targets": targets,
            "raw_obj_id": self.get_raw_obj_id(),    
            "raw_obj_status": self.get_raw_obj_status(),    
            "alert_time": self.alert_time,
            "is_recovery_task": 1,
            "customer_id": self.customer_id,
            "customer_name": self.customer_name,
            "store_id": self.store_id,
            "store_name": self.store_name,
            "method": method,
            "plan_time": datetime.now(),
        }
        return OutPushTask(**kw)
    
    def get_raw_obj_id(self) -> int:
        """
        获取原始对象ID
        :return:
        """
        raise NotImplementedError("请在子类中实现该方法")
    
    def get_raw_obj_status(self) -> int:
        """
        返回原始对象的状态
        """
        raise NotImplementedError("请在子类中实现该方法")

    
    def get_notification_source(self) -> NotificationSource:
        """
        获取通知来源
        :return:
        """

        raise NotImplementedError("请在子类中实现该方法")
    
    def generate_content_prefix(self) -> str:
        """
        生成内容的前缀
        """
        prefix = self.store_name
        if self.customer_name in prefix:
            pass
        else:
            # prefix = f"{self.customer_name} {self.store_name}"
            prefix = self.store_name
        return prefix
    
    def _wwx_mes(self) -> str:
        """
        生成企业微信发送消息的content
        """
        warnings.warn("请在子类中覆盖该方法")
        return self.message
    
    def _dd_mes(self) -> str:
        """
        生成钉钉发送消息的content
        """
        warnings.warn("请在子类中覆盖该方法")
        return self.message
    
    def _feishu_mes(self) -> str:
        """
        生成飞书发送消息的content
        """ 
        warnings.warn("请在子类中覆盖该方法")
        return self.message

    def _sms_mes(self) -> str:
        """
        生成短信发送消息的content
        """
        warnings.warn("请在子类中覆盖该方法")
        return self.message
    
    def _tel_mes(self) -> str:
        """
        生成电话发送消息的content
        """
        warnings.warn("请在子类中覆盖该方法")
        return self.message  
    
    def generate_content(self, method: str) -> str:
        """
        生成发送消息的content
        """
        if method == NotificationMethodName.WORK_WX_GROUP.value:
            mes = self._wwx_mes()
        elif method == NotificationMethodName.DD_GROUP.value: 
            mes = self._dd_mes()  
        elif method == NotificationMethodName.FEISHU.value:
            mes = self._feishu_mes()
        elif method == NotificationMethodName.SMS.value:
            mes = self._sms_mes()     
        elif method == NotificationMethodName.TEL.value:
            mes = self._tel_mes()
        else:
            mes = self.message
        return mes
    
    def get_notification_strategies(self, source: NotificationSource, db_session: Session) -> List[NotificationStrategyOrm]:
        """
        获取通知策略
        :param customer_id:
        :param store_id:
        :param sensor_id:
        :return:
        """
        # 旧条件，能正确运行，先保存，如果新条件出现bug可以恢复尝试一下。
        conditions = [
            or_(
                and_(
                    NotificationStrategyOrm.customer_id == self.customer_id,
                    NotificationStrategyOrm.cond.has(NotificationConditionOrm.sources.any(source.value, operator=operators.eq)),
                    NotificationStrategyOrm.cond.has(NotificationConditionOrm.store_ids.any(self.store_id, operator=operators.eq)),
                ),
                and_(
                    NotificationStrategyOrm.customer_id == self.customer_id,
                    NotificationStrategyOrm.cond.has(NotificationConditionOrm.sources.any(source.value, operator=operators.eq)),
                    NotificationStrategyOrm.cond.has(NotificationConditionOrm.store_ids == []),
                ),
            ),
            NotificationStrategyOrm.is_enabled == 1,      
            NotificationStrategyOrm.is_deleted == 0, 
        ]
        # 新条件，来自ai的优化,主要是优化写法，无性能提升，尚未经过检验
        conditions = [
            NotificationStrategyOrm.customer_id == self.customer_id,
            NotificationStrategyOrm.is_enabled == 1,      
            NotificationStrategyOrm.is_deleted == 0, 
            NotificationStrategyOrm.cond.has(NotificationConditionOrm.sources.any(source.value, operator=operators.eq)),
            or_(
                NotificationStrategyOrm.cond.has(NotificationConditionOrm.store_ids.any(self.store_id, operator=operators.eq)),
                NotificationStrategyOrm.cond.has(NotificationConditionOrm.store_ids == []),
            )
        ]
        s = select(NotificationStrategyOrm).where(
            *conditions
        ).options(
            joinedload(NotificationStrategyOrm.methods),
        )
        strategies = db_session.scalars(s).unique().all()
        return strategies
    
    async def get_notification_strategies_async(self, source: NotificationSource, db_session: AsyncSession) -> List[NotificationStrategyOrm]:
        """
        获取通知策略
        :param customer_id:
        :param store_id:
        :param sensor_id:
        :return:
        """
        # 旧条件，能正确运行，先保存，如果新条件出现bug可以恢复尝试一下。
        conditions = [
            or_(
                and_(
                    NotificationStrategyOrm.customer_id == self.customer_id,
                    NotificationStrategyOrm.cond.has(NotificationConditionOrm.sources.any(source.value, operator=operators.eq)),
                    NotificationStrategyOrm.cond.has(NotificationConditionOrm.store_ids.any(self.store_id, operator=operators.eq)),
                ),
                and_(
                    NotificationStrategyOrm.customer_id == self.customer_id,
                    NotificationStrategyOrm.cond.has(NotificationConditionOrm.sources.any(source.value, operator=operators.eq)),
                    NotificationStrategyOrm.cond.has(NotificationConditionOrm.store_ids == []),
                ),
            ),
            NotificationStrategyOrm.is_enabled == 1,      
            NotificationStrategyOrm.is_deleted == 0, 
        ]
        # 新条件，来自ai的优化,主要是优化写法，无性能提升，尚未经过检验
        conditions = [
            NotificationStrategyOrm.customer_id == self.customer_id,
            NotificationStrategyOrm.is_enabled == 1,      
            NotificationStrategyOrm.is_deleted == 0, 
            NotificationStrategyOrm.cond.has(NotificationConditionOrm.sources.any(source.value, operator=operators.eq)),
            or_(
                NotificationStrategyOrm.cond.has(NotificationConditionOrm.store_ids.any(self.store_id, operator=operators.eq)),
                NotificationStrategyOrm.cond.has(NotificationConditionOrm.store_ids == []),
            )
        ]
        s = select(NotificationStrategyOrm).where(
            *conditions
        ).options(
            joinedload(NotificationStrategyOrm.methods),
        )
        query = await db_session.scalars(s)
        strategies = query.unique().all()
        return strategies


class IoTEvent(MicServiceEvent):
    """
    传感器消息参数
    """
    sensor_id: int = Field(..., description="关联的传感器ID")
    sensor_name: str = Field(..., description="传感器名称")
    sensor_category: str = Field(..., description="传感器分类")
    prev_status: int = Field(..., description="传感器之前状态 EventStatus") # 0:正常 1:异常
    sensor_status: int = Field(..., description="传感器状态 EventStatus") # 0:正常 1:异常
    type: str = Field(..., description="传感器事件类型 SensorEventType.value")

    def recovery_event_orm(self, db_session: Session) -> IoTEventOrm:
        """
        把指定事件标记为恢复
        :param db_session: _description_
        :return: _description_
        """
        s = select(
            IoTEventOrm
        ).where(
            IoTEventOrm.sensor_id == self.sensor_id,
            IoTEventOrm.sensor_status == EventStatus.ERR.value,
            IoTEventOrm.type == self.type
        ).order_by(IoTEventOrm.alert_time.desc())
        events = db_session.scalars(s).all()
        one = None
        if not events:
            pass
        else:
            event_ids = []
            for i, event in enumerate(events):
                event_ids.append(event.id)
                event.sensor_status = 0
                event.recovery_time = datetime.now()
                if i == 0:
                    one = event 
            # cancel 之前相关的延时推送任务
            u = update(
                OutPushTaskOrm
            ).where(
                OutPushTaskOrm.event_id.in_(event_ids)
            ).values(
                status=PushTaskStatus.CANCEL.value,
                reason=TaskCancelReason.RECOVERY.value,
            )
            db_session.execute(u)
            db_session.commit()
        return one
    
    async def recovery_event_orm_async(self, db_session: AsyncSession) -> IoTEventOrm:
        """
        把指定事件标记为恢复
        :param db_session: _description_
        :return: _description_
        """
        s = select(
            IoTEventOrm
        ).where(
            IoTEventOrm.sensor_id == self.sensor_id,
            IoTEventOrm.sensor_status == EventStatus.ERR.value,
            IoTEventOrm.type == self.type
        ).order_by(IoTEventOrm.alert_time.desc())
        query = await db_session.scalars(s)
        events = query.all()
        one = None
        if not events:
            pass
        else:
            event_ids = []
            for i, event in enumerate(events):
                event_ids.append(event.id)
                event.sensor_status = 0
                event.recovery_time = datetime.now()
                if i == 0:
                    one = event 
            # cancel 之前相关的延时推送任务
            u = update(
                OutPushTaskOrm
            ).where(
                OutPushTaskOrm.event_id.in_(event_ids)
            ).values(
                status=PushTaskStatus.CANCEL.value,
                reason=TaskCancelReason.RECOVERY.value,
            )
            await db_session.execute(u)
            await db_session.commit()
        return one
    
    def get_notification_source(self) -> NotificationSource:
        """
        获取通知来源
        :return:
        """
        if self.sensor_category == "温度传感器":
            if self.sensor_status == 1:
                # 传感器离线
                source = NotificationSource.T_SENSOR_OFFLINE
            else: # 传感器值异常
                # 传感器值异常
                source = NotificationSource.T_SENSOR_THRESHOLD
        else:  # 电力传感器
            if self.sensor_status == 1:
                # 传感器离线
                source = NotificationSource.E_SENSOR_OFFLINE
            else: # 传感器值异常
                # 传感器值异常
                source = NotificationSource.E_SENSOR_THRESHOLD
        return source
    
    def get_raw_obj_id(self) -> int:
        """
        获取原始对象ID
        :return:
        """
        return self.sensor_id
    
    def get_raw_obj_status(self) -> int:
        """
        返回原始对象的状态
        """
        return self.sensor_status
    
    def get_mes_type_status_desc(self) -> Tuple[str, str]:
        """
        获取类型、状态、事件说明
        :return:
        """
        if self.sensor_status == 0:
            status = "正常"
            if self.type == SensorEventType.THRESHOLD.value:
                desc = f"检测到{self.sensor_category} {self.sensor_name} 的数据已恢复至正常范围"
            else:
                desc = f"{self.sensor_category} {self.sensor_name} 已恢复在线"
        else:
            status = "异常"
            if self.type == SensorEventType.THRESHOLD.value:
                desc = f"检测到{self.sensor_category} {self.sensor_name} 的数据超出正常范围"
            else:
                desc = f"{self.sensor_category} {self.sensor_name} 离线"
        return status, desc
    
    def _wwx_mes(self):
        """
        生成企业微信消息
        :return:
        """
        status, desc = self.get_mes_type_status_desc()
        if self.recovery_time:
            color, suffix, current_value = "#22C55E", "恢复", self.end_value
        else:
            color, suffix, current_value = "#F87171", "提醒", self.begin_value
        mes = f"""
        ##### AIOps系统 {self.mes_type}事件{suffix}
        > 来源: <font color="info">{self.sensor_category}</font>
        > 品牌: <font color="comment">{self.customer_name}</font>
        > 门店: <font color="comment">{self.store_name}</font>
        > 事件: <font color="comment">节点离线</font>
        > 事件说明: <font color="comment">{desc}</font>
        > 当前状态: <font color="{color}">{status}</font>
        > 当前值: <font color="{color}">{current_value}</font>
        > 发生时间: <font color="comment">{self.alert_time.strftime('%Y-%m-%d %H:%M:%S')}</font>
        > 恢复时间: <font color="comment">{self.recovery_time.strftime('%Y-%m-%d %H:%M:%S') if self.recovery_time else '暂无'}</font>
        > [登录查看详情](https://brand.ihcc.com.cn/login)
        """
        return mes
    
    def _dd_mes(self):
        """
        生成钉钉消息的内容，钉钉消息比较严格，有如下要求:
        * 行前面不能有空格
        * 末尾必须是2个空格加换行符
        :return: _description_
        """
        status, desc = self.get_mes_type_status_desc()
        if self.recovery_time:
            color, suffix, current_value = "#22C55E", "恢复", self.end_value
        else:
            color, suffix, current_value = "#F87171", "提醒", self.begin_value
        mes = "  \n".join([
            f'### AIOps系统 {self.mes_type}事件{suffix}',
            f'**来源**: <font color="#0070F3">{self.sensor_category}</font>',
            f'**门店**: <font color="#333333">{self.store_name}</font>',
            f'**事件**: <font color="#333333">{self.type}</font>',
            f'**事件说明**: <font color="#333333">{desc}</font>',
            f'**当前状态**: <font color="{color}">{status}</font>',
            f'**当前值**: <font color="{color}">{current_value}</font>',
            f'**发生时间**: <font color="#333333">{self.alert_time.strftime("%Y-%m-%d %H:%M:%S")}</font>',
            f'**恢复时间**: <font color="#333333">{self.recovery_time.strftime("%Y-%m-%d %H:%M:%S") if self.recovery_time else "暂无"}</font>',
            f'[登录查看详情](https://brand.ihcc.com.cn/login)'
        ])
        return mes
    
    def _sms_mes(self) -> str:
        """
        生成短信消息
        :return:
        """
        if self.sensor_status == 1:
            mes = f"AIOps系统{self.mes_type}信息: {self.customer_name} {self.store_name} 的  {self.sensor_category} {self.sensor_name} 于 {self.alert_time.strftime('%Y-%m-%d %H:%M:%S')} 发生 {self.type} 事件，敬请留意."
        else:
            mes = f"AIOps系统{self.mes_type}信息: {self.customer_name} {self.store_name} 的 {self.sensor_category} {self.sensor_name} 之前发生的{self.type} 事件已于{self.recovery_time.strftime('%Y-%m-%d %H:%M:%S')}恢复."
        return mes
    

class GatewayEvent(MicServiceEvent):
    """
    网关设备的消息参数
    """
    # gateway_id: int | None = Field(None, description="关联的网关ID 这个值不一定准确，不要依赖")
    gateway_status: int = Field(..., description="网关状态 EventStatus, 也是trigger_value") # 0:正常 1:问题
    trigger_name: str = Field(..., description="触发事件名称")
    trigger_type: str = Field(..., description="触发事件类型")
    trigger_source: str = Field(..., description="触发事件来源")
    gateway_ip: str = Field(..., description="zabbix主机对应的host")
    raw_obj_id: Any | None = Field(None, description="原始对象ID")

    def recovery_event_orm(self, db_session: Session) -> GatewayEventOrm:
        """
        把指定事件标记为恢复
        :param db_session: _description_
        :return: _description_
        """
        b1 = time.perf_counter()
        s = select(
            GatewayEventOrm
        ).where(
            GatewayEventOrm.trigger_name == self.trigger_name,
            GatewayEventOrm.store_id == self.store_id,
            GatewayEventOrm.gateway_status == EventStatus.ERR.value,
        ).order_by(GatewayEventOrm.alert_time.desc())
        events = db_session.scalars(s).all()
        one = None
        if not events:  # 没有找到之前的记录，忽略
            pass
        else:
            event_ids = []
            for i, event in enumerate(events):
                event_ids.append(event.id)
                event.gateway_status = 0
                event.recovery_time = datetime.now()
                if i == 0:
                    one = event
            # cancel 之前相关的延时推送任务
            u = update(
                OutPushTaskOrm
            ).where(
                OutPushTaskOrm.event_id.in_(event_ids)
            ).values(
                status=PushTaskStatus.CANCEL.value,
                reason=TaskCancelReason.RECOVERY.value,
            )
            db_session.execute(u)
            db_session.commit()
        b2 = time.perf_counter()
        logger.info(f"recovery_event_orm 函数耗时: {(b2 - b1) * 1000:.2f}ms")
        return one
    
    async def recovery_event_orm_async(self, db_session: AsyncSession) -> GatewayEventOrm:
        """
        把指定事件标记为恢复
        :param db_session: _description_
        :return: _description_
        """
        b1 = time.perf_counter()
        s = select(
            GatewayEventOrm
        ).where(
            GatewayEventOrm.trigger_name == self.trigger_name,
            GatewayEventOrm.store_id == self.store_id,
            GatewayEventOrm.gateway_status == EventStatus.ERR.value,
        ).order_by(GatewayEventOrm.alert_time.desc())
        query = await db_session.scalars(s)
        events = query.all()
        one = None
        if not events:  # 没有找到之前的记录，忽略
            pass
        else:
            event_ids = []
            now = datetime.now()
            for i, event in enumerate(events):
                event_ids.append(event.id)
                event.gateway_status = 0
                event.recovery_time = now
                if i == 0:
                    one = event
            # cancel 之前相关的延时推送任务
            u = update(
                OutPushTaskOrm
            ).where(
                OutPushTaskOrm.event_id.in_(event_ids)
            ).values(
                status=PushTaskStatus.CANCEL.value,
                reason=TaskCancelReason.RECOVERY.value,
            )
            await db_session.execute(u)
            await db_session.commit()
        b2 = time.perf_counter()
        logger.info(f"recovery_event_orm 函数耗时: {(b2 - b1) * 1000:.2f}ms")
        return one
    
    def get_notification_source(self) -> NotificationSource:
        """
        获取通知来源
        :return:
        """
        return NotificationSource.GATEWAY_OFFLINE
    
    def get_raw_obj_id(self) -> int:
        """
        获取原始对象ID
        :return:
        """
        return self.raw_obj_id
    
    def get_raw_obj_status(self) -> int:
        """
        返回原始对象的状态
        """
        return self.gateway_status
    
    def get_mes_type_status_desc(self) -> Tuple[str, str]:
        """
        获取类型、状态、事件说明
        :return:
        """
        if self.gateway_status == 0:
            status = "正常"
        else:
            status = "异常"
        desc = self.trigger_name
        return status, desc

    def _wwx_mes(self) -> str:
        """
        生成企业微信消息
        :return:
        """
        status, desc = self.get_mes_type_status_desc()
        if self.recovery_time:
            color, suffix, current_value = "info", "恢复", self.end_value
        else:
            color, suffix, current_value = "warning", "提醒", self.begin_value
        mes = f"""
        ##### AIOps系统 {self.mes_type}事件{suffix}
        > 来源: <font color="info">{self.trigger_source}</font>
        > 品牌: <font color="comment">{self.customer_name}</font>
        > 门店: <font color="comment">{self.store_name}</font>
        > 事件: <font color="comment">{self.trigger_type}</font>
        > 事件说明: <font color="comment">{desc}</font>
        > 当前状态: <font color="{color}">{status}</font>
        > 当前值: <font color="{color}">{current_value}</font>
        > 发生时间: <font color="comment">{self.alert_time.strftime('%Y-%m-%d %H:%M:%S')}</font>
        > 恢复时间: <font color="comment">{self.recovery_time.strftime('%Y-%m-%d %H:%M:%S') if self.recovery_time else '暂无'}</font>
        > [登录查看详情](https://brand.ihcc.com.cn/login)
        """
        return mes
    
    def _dd_mes(self):
        """
        生成钉钉消息的内容，钉钉消息比较严格，有如下要求:
        * 行前面不能有空格
        * 末尾必须是2个空格加换行符
        :return: _description_
        """
        status, desc = self.get_mes_type_status_desc()
        if self.recovery_time:
            color, suffix, current_value = "#22C55E", "恢复", self.end_value
        else:
            color, suffix, current_value = "#F87171", "提醒", self.begin_value
        mes = "  \n".join([
            f'### AIOps系统 {self.mes_type}事件{suffix}',
            f'**来源**: <font color="#0070F3">{self.trigger_source}</font>',
            f'**门店**: <font color="#333333">{self.store_name}</font>',
            f'**事件**: <font color="#333333">{self.trigger_type}</font>',
            f'**事件说明**: <font color="#333333">{desc}</font>',
            f'**当前状态**: <font color="{color}">{status}</font>',
            f'**当前值**: <font color="{color}">{current_value}</font>',
            f'**发生时间**: <font color="#333333">{self.alert_time.strftime("%Y-%m-%d %H:%M:%S")}</font>',
            f'**恢复时间**: <font color="#333333">{self.recovery_time.strftime("%Y-%m-%d %H:%M:%S") if self.recovery_time else "暂无"}</font>',
            f'[登录查看详情](https://brand.ihcc.com.cn/login)'
        ])
        return mes
    
    def _sms_mes(self) -> str:
        """
        生成短信消息
        :return:
        """
        if self.gateway_status == 1:
            mes = f"AIOps系统{self.mes_type}信息: {self.customer_name} {self.store_name} 的 {self.trigger_source} 于 {self.alert_time.strftime('%Y-%m-%d %H:%M:%S')} 发生 {self.event_name} 事件，敬请留意."
        else:
            mes = f"AIOps系统{self.mes_type}信息: {self.customer_name} {self.store_name} 的 {self.trigger_source}的{self.trigger_name}已于{self.recovery_time.strftime('%Y-%m-%d %H:%M:%S')}恢复."
        return mes

class LanEvent(MicServiceEvent):
    """
    内网设备消息参数
    """
    node_id: str = Field(..., description="关联的内网节点ID")
    node_name: str = Field(..., description="内网节点名称")
    device_type: str = Field(..., description="节点设备类型")
    node_status: int = Field(..., description="节点状态 EventStatus") # 0:在线 1:离线 和web-extend-api的定义正好相反
    node_ip: str = Field(..., description="节点IP")

    def recovery_event_orm(self, db_session: Session) -> LanEventOrm:
        """
        把指定事件标记为恢复
        :param db_session: _description_
        :return: _description_
        """
        s = select(
            LanEventOrm
        ).where(
            LanEventOrm.node_id == self.node_id,
            LanEventOrm.node_status == EventStatus.ERR.value,
        ).order_by(LanEventOrm.alert_time.desc())
        events = db_session.scalars(s).all()
        one = None
        if not events:
            pass
        else:
            event_ids = []
            for i, event in enumerate(events):
                event_ids.append(event.id)
                event.node_status = 0
                event.recovery_time = datetime.now()
                if i == 0:
                    one = event
            # cancel 之前相关的延时推送任务
            u = update(
                OutPushTaskOrm
            ).where(
                OutPushTaskOrm.event_id.in_(event_ids)
            ).values(
                status=PushTaskStatus.CANCEL.value,
                reason=TaskCancelReason.RECOVERY.value,
            )
            db_session.execute(u)
            db_session.commit()
        return one
    
    async def recovery_event_orm_async(self, db_session: AsyncSession) -> LanEventOrm:
        """
        把指定事件标记为恢复
        :param db_session: _description_
        :return: _description_
        """
        s = select(
            LanEventOrm
        ).where(
            LanEventOrm.node_id == self.node_id,
            LanEventOrm.node_status == EventStatus.ERR.value,
        ).order_by(LanEventOrm.alert_time.desc())
        query = await db_session.scalars(s)
        events = query.all()
        one = None
        if not events:
            pass
        else:
            event_ids = []
            for i, event in enumerate(events):
                event_ids.append(event.id)
                event.node_status = 0
                event.recovery_time = datetime.now()
                if i == 0:
                    one = event
            # cancel 之前相关的延时推送任务
            u = update(
                OutPushTaskOrm
            ).where(
                OutPushTaskOrm.event_id.in_(event_ids)
            ).values(
                status=PushTaskStatus.CANCEL.value,
                reason=TaskCancelReason.RECOVERY.value,
            )
            await db_session.execute(u)
            await db_session.commit()
        return one
    
    def get_notification_source(self) -> NotificationSource:
        """
        获取通知来源
        :return:
        """
        return NotificationSource.LAN_OFFLINE
    
    def get_raw_obj_id(self) -> int:
        """
        获取原始对象ID
        :return:
        """
        return self.node_id
    
    def get_raw_obj_status(self) -> int:
        """
        返回原始对象的状态
        """
        return self.node_status
    
    def get_mes_type_status_desc(self) -> Tuple[str, str]:
        """
        获取类型、状态、事件说明
        :return:
        """
        if self.node_status == 0:
            status = "正常"
            desc = f"设备 {self.node_name} 已恢复在线状态"
        else:
            status = "异常"
            desc = f"设备 {self.node_name} 离线"
        return status, desc
    
    def _wwx_mes(self):
        """
        生成企业微信消息
        :return:
        """
        
        status, desc = self.get_mes_type_status_desc()
        if self.recovery_time:
            color, suffix, current_value = "info", "恢复", "online"
        else:
            color, suffix, current_value = "warning", "提醒", "offline"
        mes = f"""
        ##### AIOps系统 {self.mes_type}事件{suffix}
        > 来源: <font color="info">内部网络</font>
        > 品牌: <font color="comment">{self.customer_name}</font>
        > 门店: <font color="comment">{self.store_name}</font>
        > 事件: <font color="comment">节点离线</font>
        > 事件说明: <font color="comment">{desc}</font>
        > 当前状态: <font color="{color}">{status}</font>
        > 当前值: <font color="{color}">{current_value}</font>
        > 发生时间: <font color="comment">{self.alert_time.strftime('%Y-%m-%d %H:%M:%S')}</font>
        > 恢复时间: <font color="comment">{self.recovery_time.strftime('%Y-%m-%d %H:%M:%S') if self.recovery_time else '暂无'}</font>
        > [登录查看详情](https://brand.ihcc.com.cn/login)
        """
        return mes
    
    def _dd_mes(self):
        """
        生成钉钉消息的内容，钉钉消息比较严格，有如下要求:
        * 行前面不能有空格
        * 末尾必须是2个空格加换行符
        :return: _description_
        """
        status, desc = self.get_mes_type_status_desc()
        if self.recovery_time:
            color, suffix, current_value = "#22C55E", "恢复", "online"
        else:
            color, suffix, current_value = "#F87171", "提醒", "offline"
        mes = "  \n".join([
            f'### AIOps系统 {self.mes_type}事件{suffix}',
            f'**来源**: <font color="#0070F3">内部网络</font>',
            f'**门店**: <font color="#333333">{self.store_name}</font>',
            f'**事件**: <font color="#333333">设备离线</font>',
            f'**事件说明**: <font color="#333333">{desc}</font>',
            f'**当前状态**: <font color="{color}">{status}</font>',
            f'**当前值**: <font color="{color}">{current_value}</font>',
            f'**发生时间**: <font color="#333333">{self.alert_time.strftime("%Y-%m-%d %H:%M:%S")}</font>',
            f'**恢复时间**: <font color="#333333">{self.recovery_time.strftime("%Y-%m-%d %H:%M:%S") if self.recovery_time else "暂无"}</font>',
            f'[登录查看详情](https://brand.ihcc.com.cn/login)'
        ])
        return mes
    
    def _sms_mes(self) -> str:
        """
        生成短信消息
        :return:
        """
        if self.node_status == 1:
            mes = f"AIOps系统{self.mes_type}信息: {self.customer_name} {self.store_name} 的 {self.node_name} 于 {self.alert_time.strftime('%Y-%m-%d %H:%M:%S')} 离线，敬请留意."
        else:
            mes = f"AIOps系统{self.mes_type}信息: {self.customer_name} {self.store_name} 的 {self.node_name}已于{self.recovery_time.strftime('%Y-%m-%d %H:%M:%S')}恢复."
        return mes

@CELERY_APP.task
def send_notification(task: dict) -> str:
    """
    发送通知
    :param notification:
    :return:
    """
    task_id = task.get("id")
    resp = Resp()
    try:
        task: OutPushTask = OutPushTask(**task)
    except Exception as e:
        logger.error(e)
    if not isinstance(task, OutPushTask):
        resp.data = f"参数初始化失败: task: {task}"
        logger.error(mes)
    else:
        logger.warning(f"开始执行发送任务...")
        if task.is_recovery_task == 1:  # 如果是恢复任务
            logger.warning(f"开始发送恢复通知: {task}")
            task.execute()
            mes = f"恢复通知发送完成: {task}"
            logger.warning(mes)
        else:  # 如果是告警任务
            resp = task.alert_is_recovery()  # 再次检查，因为如果是一个延时任务，那么在执行时，可能已经恢复了
            if not resp:
                mes = f"检查事件状态失败: {resp.message}"
                logger.error(f"{mes}, task_id = {task.id}")
            elif  resp.data == 0:
                mes = f"告警已恢复，不再发送通知: {task.model_dump()}"
                logger.warning(mes)
            else:
                logger.warning(f"开始发送告警通知: {task}")
                task.execute()
                mes = f"告警通知发送完成: {task}"
                logger.warning(mes)
    if task_id and not resp:  # 如果发送失败，标记任务失败
        OutPushTaskOrm.mark_fail(id=task_id, reason=resp.message)  
    return mes


@CELERY_APP.task
def demo_func(receive_time: datetime):
    now = datetime.now()
    logger.debug(f"测试函数于{now},执行，延时：{(now - receive_time).total_seconds()}")


async def receive_mic_service_event(params: MicServiceEvent) -> None:
    """
    接收微服务发来的事件
    :param params:
    :return:
    """
    b1 = time.perf_counter()
    params.id = None  # 避免其他微服务在发送请求时无意中夹带id字段造成的困惑
    await params.generate_task_async()  # 生成任务
    b2 = time.perf_counter()
    logger.debug(f"接收微服务事件耗时: {(b2 - b1) * 1000:.2f} ms")


def page_send_records(keyword: str | None, 
                      method: NotificationMethodName | None = None,
                      target: str | None = None,
                      customer_id: int | None = None,
                      store_id: str | None = None,
                      start_time: datetime | None = None,
                      end_time: datetime | None = None,
                      success: int | None = None,
                      page_num: int = 1,
                      page_size: int = 10) -> Resp:
    """
    分页查询发送记录
    """
    db_session, resp = new_db_session(sync=True), Resp()
    s = select(SendMessageRecordOrm)
    if keyword:
        s = s.where(or_(
            SendMessageRecordOrm.title.like(f"%{keyword}%"), 
            SendMessageRecordOrm.title.like(f"%{keyword}%"), 
            SendMessageRecordOrm.content.like(f"%{keyword}%"), 
            SendMessageRecordOrm.store_name.like(f"%{keyword}%")
            ))
    if method:
        s = s.where(SendMessageRecordOrm.method == method.value)
    if target:
        s = s.where(SendMessageRecordOrm.target == target)
    if customer_id:
        s = s.where(SendMessageRecordOrm.customer_id == customer_id)
    if store_id:
        s = s.where(SendMessageRecordOrm.store_id == store_id)
    if start_time:
        s = s.where(SendMessageRecordOrm.send_time >= start_time)
    if end_time:
        s = s.where(SendMessageRecordOrm.send_time <= end_time)
    if success is not None:
        s = s.where(SendMessageRecordOrm.success == success)
    total = db_session.scalar(select(func.count()).select_from(s))
    s = s.order_by(SendMessageRecordOrm.send_time.desc())
    s = s.offset((page_num - 1) * page_size).limit(page_size)
    data = [x.to_dict(to_plain=True) for x in db_session.scalars(s)]
    db_session.close()
    resp.data = paging_data(total=total, data=data, page_num=page_num, page_size=page_size)
    return resp


async def page_send_records_async(keyword: str | None = None,
                                  method: NotificationMethodName | None = None,
                                  target: str | None = None,
                                  customer_ids: List[int] | None = None,
                                  store_ids: List[int] | None = None,
                                  start_time: datetime | None = None,
                                  end_time: datetime | None = None,
                                  success: int | None = None,
                                  page_num: int = 1,
                                  page_size: int = 10) -> Resp:
    """
    分页查询发送记录
    """
    db_session, resp = new_db_session(sync=True), Resp()
    s = select(SendMessageRecordOrm)
    if keyword:
        s = s.where(or_(
            SendMessageRecordOrm.title.like(f"%{keyword}%"), 
            SendMessageRecordOrm.title.like(f"%{keyword}%"), 
            SendMessageRecordOrm.content.like(f"%{keyword}%"), 
            SendMessageRecordOrm.store_name.like(f"%{keyword}%")
            ))
    if method:
        s = s.where(SendMessageRecordOrm.method == method.value)
    if target:
        s = s.where(SendMessageRecordOrm.target == target)
    if customer_ids:
        s = s.where(SendMessageRecordOrm.customer_id.in_(customer_ids))
    if store_ids:
        s = s.where(SendMessageRecordOrm.store_id.in_(store_ids))
    if start_time:
        s = s.where(SendMessageRecordOrm.send_time >= start_time)
    if end_time:
        s = s.where(SendMessageRecordOrm.send_time <= end_time)
    if success is not None:
        s = s.where(SendMessageRecordOrm.success == success)
    total = db_session.scalar(select(func.count()).select_from(s))
    s = s.order_by(SendMessageRecordOrm.send_time.desc())
    s = s.offset((page_num - 1) * page_size).limit(page_size)
    data = [x.to_dict(to_plain=True) for x in db_session.scalars(s)]
    db_session.close()
    resp.data = paging_data(total=total, data=data, page_num=page_num, page_size=page_size)
    return resp


def get_push_message_setting_summary(customer_id: int | None = None) -> Resp:
    """
    获取推送消息设置的摘要
    覆盖了多少门店，多少方式，多少目标
    """
    db_session, resp = new_db_session(sync=True), Resp()
    sub = select(
        NotificationStrategyOrm.customer_id.label("customer_id"),
        func.unnest(NotificationConditionOrm.store_ids).label("store_ids")
    ).join_from(
        NotificationConditionOrm, NotificationStrategyOrm, NotificationStrategyOrm.cond_id == NotificationConditionOrm.id
    ).where(
        NotificationStrategyOrm.is_deleted == 0,
    ).group_by(
        NotificationStrategyOrm.customer_id,
        NotificationConditionOrm.store_ids
    )
    sub2 = select(
        NotificationStrategyOrm.customer_id.label("customer_id"),
        func.count(NotificationStrategyOrm.id).label("total"),
        func.sum(NotificationStrategyOrm.is_enabled).label("enabled"),
    ).where(
        NotificationStrategyOrm.is_deleted == 0,
    ).group_by(
        NotificationStrategyOrm.customer_id
    )
    if customer_id:
        sub = sub.where(NotificationConditionOrm.customer_id == customer_id)
        sub2 = sub2.where(NotificationStrategyOrm.customer_id == customer_id)
    sub = sub.subquery()
    sub2 = sub2.subquery()
    s = select(
        func.count(distinct(sub.c.store_ids)),
        func.coalesce(sub2.c.enabled, 0),
        func.coalesce(sub2.c.total, 0),
        ).join_from(
        sub, sub2, sub.c.customer_id == sub2.c.customer_id
        ).group_by(
        sub2.c.customer_id,
        sub2.c.enabled,
        sub2.c.total,
        )
    result = db_session.execute(s).fetchone()
    db_session.close()
    if result:
        store_num, enabled_num, total_num = result  # 覆盖了多少门店，开启策略数目，策略总数
    else:
        store_num, enabled_num, total_num = 0, 0, 0
    resp.data = {
        "store_num": store_num,
        "enabled_num": enabled_num,
        "total_num": total_num,
    }
    return resp
            

if __name__ == '__main__':
    get_push_message_setting_summary(83)
    pass